home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Turnbull China Bikeride
/
Turnbull China Bikeride - Disc 2.iso
/
STUTTGART
/
LANG
/
GNUST
/
Docs
/
tutorial
< prev
Wrap
Text File
|
1994-06-24
|
135KB
|
3,697 lines
A Tutorial for GNU Smalltalk
Andrew Valencia
Valencia Consulting
Overview
What this manual presents
This document provides a tutorial introduction to the
Smalltalk language in general, and the GNU Smalltalk imple-
mentation in particular. It does not provide exhaustive
coverage of every feature of the language and its libraries;
instead, it attempts to introduce a critical mass of ideas
and techniques to get the Smalltalk novice moving in the
right direction.
Who this manual is written for
This manual assumes that the reader is acquainted with
the basics of computer science, and has reasonable profi-
ciency with a procedural language such as C. It also
assumes that the reader is already familiar with the usual
janitorial tasks associated with programming-editing, moving
files, and so forth.
1. Getting started
1.1. Starting up Smalltalk
Assuming that GNU Smalltalk has been installed on your
system, starting it is as simple as:
% mst
the system loads in Smalltalk, and displays a startup banner
like:
Smalltalk 1.1.1 Ready
st>
You are now ready to try your hand at Smalltalk! By the
way, when you're ready to quit, you exit Smalltalk by typing
control-D on an empty line.
1.2. Saying hello
An initial exercise is to make Smalltalk say "hello" to
you. Type in the following line ("printNl" is a upper case
N and a lower case L):
'Hello, world' printNl !
The system then prints back 'Hello, world' to you.1
-2-
1.3. What actually happened
The front-line Smalltalk interpreter gathers all text
until a '!' character and executes it. So the actual
Smalltalk code executed was:
'Hello, world' printNl
This code does two things. First, it creates an object of
type "String" which contains the characters "Hello, world".
Second, it sends the message named "printNl" to the object.
When the object is done processing the message, the code is
done and we get our prompt back.
You'll notice that we didn't say anything about print-
ing the string, even though that's in fact what happened.
This was very much on purpose-the code we typed in doesn't
know anything about printing strings. It knew how to get a
string object, and it knew how to send a message to that
object. That's the end of the story for the code we wrote.
But for fun, let's take a look at what happened when the
string object received the "printNl" message.
1.4. What a string does with a "printNl" message
The string object containing "Hello, world" was sent
the message "printNl". It then goes to a table2 which lists
1 It also prints out a lot of statistics.
Ignore these; they provide information on the per-
formance of the underlying Smalltalk engine. You
can inhibit them by starting Smalltalk as:
% mst -q
2 Which table? This is determined by the type
of the object. An object has a type, known as the
class to which it belongs. Each class has a table
of methods. For the object we created, it is
known as a member of the "String" class. So we go
-3-
the messages which strings can receive, and what code to
execute. It finds that there is indeed an entry for
"printNl" and runs this code. This code then walks through
its characters, printing each of them out to the terminal.3
The central point is that an object is entirely self-
contained; only the object knew how to print itself out.
When we want an object to print out, we ask the object
itself to do the printing.
1.5. Doing math
A similar piece of code prints numbers:
1234 printNl !
Notice how we used the same message, but have sent it to a
new type of object-an integer (from class "Integer"). The
way in which an integer is printed is much different from
the way a string is printed on the inside, but because we
are just sending a message, we do not have to be aware of
this. We tell it to "printNl", and it prints itself out.
As a user of an object, we can thus usually send a par-
ticular message and expect basically the same kind of behav-
ior, regardless of object's internal structure (for
instance, we have seen that sending "printNl" to an object
makes the object print itself). In later chapters we will
see a wide range of types of objects. Yet all of them can
be printed out the same way-with "printNl".
White space is ignored, except as it separates words.
This example could also have looked like:
1234
printNl !
An integer can be sent a number of messages in addition
to just printing itself. An important set of messages for
integers are the ones which do math:
to the table associated with the String class.
3 Actually, the message "printNl" was inherited
from Object. It sent a "print" message, also
inherited by Object, which then sent "printOn:" to
the object, specifying that it print to "stdout".
The String class then prints its characters to the
standard output.
-4-
(9 + 7) printNl !
Answers (correctly!) the value 16. The way that it does
this, however, is a significant departure from a procedural
language.
1.6. Math in Smalltalk
In this case, what happened was that the object "9" (an
Integer), received a "+" message with an argument of "7"
(also an Integer). The "+" message for integers then caused
Smalltalk to create a new object "16" and return it as the
resultant object. This "16" object was then given the
"printNl" message, and printed "16" on the terminal.
Thus, math is not a special case in Smalltalk; it is
done exactly like everything else-by creating objects, and
sending them messages. This may seem odd to the Smalltalk
novice, but this regularity turns out to be quite a boon-
once you've mastered just a few paradigms, all of the lan-
guage "falls into place." Before you go on to the next
chapter, make sure you try math involving "*" (multiplica-
tion), "-" (subtraction), and "/" (division) also. These
examples should get you started:
(8 * (4 / 2)) printNl !
(8 - (4 + 1)) printNl !
(5 + 4) printNl !
(2/3 + 7) printNl !
(2 + 3 * 4) printNl !
(2 + (3 * 4)) printNl !
-5-
2. Using some of the Smalltalk classes
This chapter has examples which need a place to hold
the objects they create. The following line creates such a
place; for now, treat it as magic. At the end of the chap-
ter we will revisit it with an explanation. Type in:
Smalltalk at: #x put: 0 !
Now let's create some new objects.
2.1. An array in Smalltalk
An array in Smalltalk is similar to an array in any
other language, although the syntax may seem peculiar at
first. To create an array with room for 20 elements, do:
x := Array new: 20 !
The "Array new: 20" creates the array; the "x :=" part con-
nects the name "x" with the object. Until you assign some-
thing else to "x", you can refer to this array by the name
"x".
Changing elements of the array is not done using the
":=" operator; this operator is used only to bind names to
objects. In fact, you never modify data structures;
instead, you send a message to the object, and it will mod-
ify itself. For instance:
(x at: 1) printNl !
which prints:
nil
The slots of an array are initially set to "nothing" (which
Smalltalk calls "nil"). Let's set the first slot to the
number 99:
x at: 1 put: 99 !
and now make sure the 99 is actually there:
(x at: 1) printNl !
which then prints out:
99
These examples show how to manipulate an array. They also
show the standard way in which messages are passed argu-
ments. In most cases, if a message takes an argument, its
name will end with ":".4 So when we said "x at: 1" we were
sending a message to whatever object was currently bound to
"x" with an argument of 1. For an array, this results in
the first slot of the array being returned.
The second operation, "x at: 1 put: 99" is a message
with two arguments. It tells the array to place the second
argument (99) in the slot specified by the first (1). Thus,
when we re-examine the first slot, it does indeed now
4 Alert readers will remember that the math
examples of the previous chapter deviated from
this.
-6-
contain 99.
There is a shorthand for describing the messages you
send to objects. You just run the message names together.
So we would say that our array accepts both the "at:" and
"at:put:" messages.
There is quite a bit of sanity checking built into an
array. The request
6 at: 1
fails with an error; 6 is an integer, and can't be indexed.
Further,
x at: 21
fails with an error, because the array we created only has
room for 20 objects.5 Finally, note that the object stored
in an array is just like any other object, so we can do
things like:
((x at: 1) + 1) printNl !
which (assuming you've been typing in the examples) will
print 100.
2.2. A set in Smalltalk
We're done with the array we've been using, so we'll
assign something new to our "x" variable. Note that we
don't need to do anything special about the old array-the
fact that nobody is using it any more will be automatically
detected, and the memory reclaimed.6 So, to get our new
object, simply do:
x := Set new !
Which creates an empty set. To view its contents, do:
x printNl !
the kind of object is printed out (i.e., Set), and then the
members are listed within parenthesis. Since it's empty, we
see:
Set ()
Now let's toss some stuff into it. We'll add the numbers 5
and 7, plus the string 'foo'. We could type:
5 As of release 1.1, GNU Smalltalk does not
actually catch this error.
6 This is known as "garbage collection." It is
generally done when Smalltalk finds that it is
running low on memory.
-7-
x add: 5 !
x add: 7 !
x add: 'foo' !
But let's save a little typing by using a Smalltalk short-
hand:
x add: 5; add: 7; add: 'foo' !
This line does exactly what the previous example's three
lines did. The trick is that the semicolon operator causes
the message to be sent to the same object as the last mes-
sage sent. So saying "; add: 7" is the same as saying "x
add: 7", because "x" was the last thing a message was sent
to. This may not seem like such a big savings, but compare
the ease when your variable is named "aVeryLongVariableName"
instead of just "x"! We'll revisit some other occasions
where ";" saves you trouble, but for now let's continue with
our set. Type either version of the example, and make sure
that we've added 5, 7, and "foo":
x printNl !
we'll see that it now contains our data:
Set (5 'foo' 7)
What if we add something twice? No problem-it just stays in
the set. So a set is like a big checklist-either it's in
there, or it isn't. To wit:
x add:5; add: 5; add: 5; add: 5 !
x printNl !
We've added "5" several times, but when we printed our set
back out, we just see:
Set (5 'foo' 7)
What you put into a set with "add:", you can take out
with "remove:". Try:
x remove: 5 !
x printNl !
The set now prints as:
Set ('foo' 7)
The "5" is indeed gone from the set.
We'll finish up with one more of the many things you
can do with a set- checking for membership. Try:
(x includes: 7) printNl !
(x includes: 5) printNl !
From which we see that x does indeed contain 7, but not 5.
Notice that the answer is printed as "true" or "false".
Once again, the thing returned is an object-in this case, an
object known as a boolean. We'll look at the use of
booleans later, but for now we'll just say that booleans are
nothing more than objects which can only either be true or
false-nothing else. So they're very useful for answers to
yes or no questions, like the ones we just posed. Let's
take a look at just one more kind of data structure:
2.3. Dictionaries
A dictionary is a special kind of collection. With a
regular array, you must index it with integers. With dic-
tionaries, you can index it with any object at all. Dic-
tionaries thus provide a very powerful way of correlating
-8-
one piece of information to another. Their only downside is
that they are somewhat less efficient than simple arrays.
Try the following:
x := Dictionary new.
x at: 'One' put: 1 !
x at: 'Two' put: 2 !
x at: 1 put: 'One' !
x at: 2 put: 'Two' !
This fills our dictionary in with some data. The data is
actually stored in pairs of key and value (the key is what
you give to at:-it specifies a slot; the value is what is
actually stored at that slot). Notice how we were able to
specify not only integers but also strings as both the key
and the value. In fact, we can use any kind of object we
want as either-the dictionary doesn't care.
Now we can map each key to a value:
(x at: 1) printNl !
(x at: 'Two') printNl !
which prints respectively:
'One'
2
We can also ask a dictionary to print itself:
x printNl !
which prints:
Dictionary (1,'One' 2,'Two' 'One',1 'Two',2 )
where the first member of each pair is the key, and the sec-
ond the value.
2.4. Smalltalk dictionary
If you'll remember from the beginning of the chapter,
we started out by saying:
Smalltalk at: #x put: 0 !
This code should look familiar-the at:put: message is how
we've been storing information in our own arrays and dic-
tionaries. In a Smalltalk environment the name "Smalltalk"
has been preset to point to a dictionary7 which both you and
Smalltalk can use. To see how this sharing works, we'll
first try to use a variable which Smalltalk doesn't know
about:
y := 0 !
Smalltalk complains because "y" is an unknown variable.
Using our knowledge of dictionaries, and taking advantage of
our access to Smalltalk's dictionary, we can add it our-
selves:
7 Actually, a SystemDictionary, which is just a
Dictionary with some extra hooks to run things
when Smalltalk first starts
-9-
Smalltalk at: #y put: 0 !
The only mystery left is why we're using "#y" instead of our
usual quoted string. This is one of those simple questions
whose answer runs surprisingly deep. The quick answer is
that "#y" and "'y'" are pretty much the same, except that
the former will always be the same object each time you use
it, whereas the latter can be a new string each time you do
so.8
Now that we've added "y" to Smalltalk's dictionary, we
try again:
y := 1 !
It works! Because you've added an entry for "y", Smalltalk
is now perfectly happy to let you use this new variable.
If you have some spare time, you can print out the
entire Smalltalk dictionary with:
Smalltalk printNl !
As you might suspect, this will print out quite a large list
of names! If you get tired of watching Smalltalk grind it
out, use your interrupt key (control-C, usually) to bring
Smalltalk back to interactive mode.
2.5. Closing thoughts
You've seen how Smalltalk provides you with some very
powerful data structures. You've also seen how Smalltalk
itself uses these same facilities to implement the language.
But this is only the tip of the iceberg-Smalltalk is much
more than a collection of "neat" facilities to use.
The objects and methods which are automatically avail-
able are only the beginning of the foundation on which you
build your programs-Smalltalk allows you to add your own
objects and methods into the system, and then use them along
with everything else. The art of programming in Smalltalk
is the art of looking at your problems in terms of objects,
using the existing object types to good effect, and enhanc-
ing Smalltalk with new types of objects. Now that you've
been exposed to the basics of Smalltalk manipulation, we can
begin to look at this object-oriented technique of program-
ming.
8 For more detail, please feel free to skip out
to chapter 12 and read the section "Two Flavors of
Equality" and the following section "Checking for
the Two Types of Equality."
-10-
3. The Smalltalk class hierarchy
When programming in Smalltalk, you sometimes need to
create new kinds of objects, and define what various mes-
sages will do to these objects. In the next chapter we will
create some new classes, but first we need to understand how
Smalltalk organizes the types and objects it contains.
Because this is a pure "concept" chapter, without any actual
Smalltalk code to run, we will keep it short and to the
point.
3.1. Class Object
Smalltalk organizes all of its classes as a tree hier-
archy. At the very top of this hierarchy is class "Object".
Following somewhere below it are more specific classes, such
as the ones we've worked with-strings, integers, arrays, and
so forth. They are grouped together based on their similar-
ities-for instance, types of objects which may be compared
as greater or less than each other fall under a class known
as "Magnitude".
One of the first tasks when creating a new object is to
figure out where within this hierarchy your object falls.
Coming up with an answer to this problem is at least as much
art as science, and there are no hard-and-fast rules to nail
it down. We'll take a look at three kinds of objects to
give you a feel for how this organization matters.
3.2. Animals
Imagine that we have three kinds of objects, represent-
ing "Animals", "Parrots", and "Pigs". Our messages will be
"eat", "sing", and "snort". Our first pass at inserting
these objects into the Smalltalk hierarchy would organize
them like:
Object
Animals
Parrots
Pigs
This means that Animals, Parrots, and Pigs are all direct
descendants of "Object", and are not descendants of each
other.
Now we must define how each animal responds to each
kind of message.
Animals
eat--Say "I have now eaten"
sing--Error
snort--Error
Parrots
eat--Say "I have now eaten"
sing--Say "Tweet"
snort--Error
Pigs
eat--Say "I have now eaten"
sing--Error
snort--Say "Oink"
Notice how we kept having to indicate an action for "eat".
-11-
An experienced object designer would immediately recognize
this as a clue that we haven't set up our hierarchy cor-
rectly. Let's try a different organization:
Animals
Parrots
Pigs
That is, Parrots inherit from Animals, and Pigs from Par-
rots. Now Parrots inherit all of the actions from Animals,
and Pigs from both Parrots and Animals. Because of this
inheritance, we may now define a new set of actions which
spares us the redundancy of the previous set:
Animals
eat--Say "I have now eaten"
sing--Error
snort--Error
Parrots
sing--Say "Tweet"
Pigs
snort--Say "Oink"
Because Parrots and Pigs both inherit from Animals, we have
only had to define the "eat" action once. However, we have
made one mistake in our class setup-what happens when we
tell a Pig to "sing"? It says "Tweet", because we have put
Pigs as an inheritor of Parrots. Let's try one final orga-
nization:
Animals
Parrots
Pigs
Now Parrots and Pigs inherit from Animals, but not from each
other. Let's also define one final pithy set of actions:
Animals
eat--Say "I have eaten"
Parrots
sing--Say "Tweet"
Pigs
snort--Say "Oink"
The change is just to leave out messages which are inappro-
priate. If Smalltalk detects that a message is not known by
an object or any of its ancestors, it will automatically
give an error-so you don't have to do this sort of thing
yourself. Notice that now sending "sing" to a Pig does
indeed not say "Tweet"-it will cause a Smalltalk error
instead.
3.3. The bottom line of the class hierarchy
The goal of the class hierarchy is to allow you to
organize objects into a relationship which allows a particu-
lar object to inherit the code of its ancestors. Once you
have identified an effective organization of types, you
should find that a particular technique need only be imple-
mented once, then inherited by the children below. This
keeps your code smaller, and allows you to fix a bug in a
particular algorithm in only once place-then have all users
of it just inherit the fix.
-12-
You will find your decisions for adding objects change
as you gain experience. As you become more familiar with
the existing set of objects and messages, your selections
will increasingly "fit in" with the existing ones. But even
a Smalltalk "pro" stops and thinks carefully at this stage-
so don't be daunted if your first choices seem difficult and
error-prone.
-13-
4. Creating a new class of objects
With the basic techniques presented in the preceding
chapters, we're ready do our first real Smalltalk program.
In this chapter we will construct three new types of objects
(known as "classes"), using the Smalltalk technique of
inheritance to tie the classes together, create new objects
belonging to these classes (known as creating instances of
the class), and send messages to these objects.
We'll exercise all this by implementing a toy home-
finance accounting system. We will keep track of our over-
all cash, and will have special handling for our checking
and savings accounts. From this point on, we will be defin-
ing classes which will be used in future chapters. Since
you will probably not be running this whole tutorial in one
Smalltalk session, it would be nice to save off the state of
Smalltalk and resume it without having to retype all the
previous examples. To save the current state of GNU
Smalltalk, type:
Smalltalk snapshot: 'myimage.img' !
and from your shell, to later restart Smalltalk from this
"snapshot":
% mst -I myimage.img
Such a snapshot currently takes a little over 300K bytes,
and contains all variables, classes, and definitions you
have added.
4.1. Creating a new class
Guess how you create a new class? This should be get-
ting monotonous by now-by sending a message to an object.
The way we create our first "custom" class is by sending the
following message:
Object subclass: #Account
instanceVariableNames: 'balance'
classVariableNames: ''
poolDictionaries: ''
category: nil !
Quite a mouthful, isn't it? Most people end up customizing
their editor to pop this up at a push of a button. But con-
ceptually, it isn't really that bad. The Smalltalk variable
"Object" is bound to the grand-daddy of all classes on the
system. What we're doing here is telling the "Object" class
that we want to add to it a subclass known as "Account".
The other parts of the message can be ignored, but
"instanceVariableNames: 'balance'" tells it that each object
in this subclass will have a hidden variable named
"balance".9
9 In case you're having a hard time making out
the font, the "''"'s after classVariableNames: and
-14-
4.2. Documenting the class
The next step is to associate a description with the
class. You do this by sending a message to the new class:
Account comment: 'I represent a place to deposit and withdraw money' !
A description is associated with every Smalltalk class, and
it's considered good form to add a description to each new
class you define. To get the description for a given class:
(Account comment) printNl !
And your string is printed back to you. Try this with class
Integer, too:
(Integer comment) printNl !
4.3. Defining a method for the class
We have created a class, but it isn't ready to do any
work for us-we have to define some messages which the class
can process first. We'll start at the beginning by defining
methods for instance creation:
!Account class methodsFor: 'instance creation'!
new
| r |
r := super new.
r init.
^r
!!
Again, programming your editor to do this is recommended.
The important points about this are "Account class", which
means that we are defining messages which are to be sent to
the Account class itself. "methodsFor: 'instance creation'"
is more documentation support; it says that all of the meth-
ods defined will be to support creating objects of type
Account. Finally, the text starting with "new" and ending
with "!!" defined what action to take for the message "new".
When you enter this definition, GNU Smalltalk will simply
give you another prompt. You method has been compiled in
and is ready for use. GNU Smalltalk is pretty quiet on suc-
cessful method definitions--but you'll get plenty of error
messages if there's a problem!
This is also the first example where we've had to use
more than one statement, and thus a good place to present
the statement separator-the ".". Like Pascal, and unlike C,
statements are separated rather than terminated. Thus you
need only use the "." when you have finished one statement
and are starting another. This is why our last statement,
"^r", does not have a "." following.
poolDictionaries: are a pair of single quotes-an
empty string.
-15-
The best way to describe how this method works is to
step through it. Imagine we sent a message to the new class
Account with the command line:
Account new !
"Account" receives the message "new" and looks up how
to process this message. It finds our new definition, and
starts running it. The first line, "| r |", creates a local
variable named "r" which can be used as a placeholder for
the objects we create. "r" will go away as soon as the mes-
sage is done being processed.
The first real step is to actually create the object.
The line "r := super new" does this using a fancy trick.
The word "super" stands for the same object that the message
"new" was originally sent to (remember-it's "Account"),
except that when Smalltalk goes to search for the methods,
he starts one level higher up in the hierarchy than the cur-
rent level. So for a method in the Account class, this is
the Object class (because the class Account inherits from is
Object-go back and look at how we created the Account
class), and the Object class' methods then execute some code
in response to the "new" message. As it turns out, Object
will do the actual creation of the object when sent a "new"
message.
One more time in slow motion: the Account method "new"
wants to do some fiddling about when new objects are cre-
ated, but he also wants to let his parent do some work with
a method of the same name. By saying "r := super new" he is
letting his parent create the object, and then he is attach-
ing it to the variable "r". So after this line of code exe-
cutes, we have a brand new object of type Account, and "r"
is bound to it. You will understand this better as time
goes on, but for now scratch your head once, accept it as a
recipe, and keep going.
We have the new object, but we haven't set it up cor-
rectly. Remember the hidden variable "balance" which we saw
in the beginning of this chapter? "super new" gives us the
object with the "balance" field containing nothing-we want
our balance field to start at 0.10 So what we need to do is
10 And unlike C, Smalltalk draws a distinction
between 0 and nil. nil is the "nothing" object,
and you will receive an error if you try to do,
say, math on it. It really does matter that we
initialize our instance variable to the number 0
-16-
ask the object to set itself up. By saying "r init", we are
sending the "init" message to our new Account. We'll define
this method in the next section-for now just assume that
sending the "init" message will get our Account set up.
Finally, we say "^r". In English, this is "return what
r is attached to". This means that whoever sent "Account"
the "new" message will get back this brand new account. At
the same time, our temporary variable "r" ceases to exist.
4.4. Defining an instance method
We need to define the "init" method for our Account
objects, so that our "new" method defined above will work.
Here's the Smalltalk code:
!Account methodsFor: 'instance initialization'!
init
balance := 0
!!
It looks quite a bit like the previous method definition,
except that the first one said "Account class methods-
For:...", and ours says "Account methodsFor:...". The dif-
ference is that the first one defined a method for messages
sent directly to "Account", but the second one is for mes-
sages which are sent to Account objects once they are cre-
ated.
The method named "init" has only one line, "balance :=
0". This initializes the hidden variable "balance" (actu-
ally called an instance variable ) to zero, which makes
sense for an account balance. Notice that the method
doesn't end with "^r" or anything like it-this method
doesn't return a value to the message sender. When you do
not specify a return value, Smalltalk defaults the return
value to the object currently executing. For clarity of
programming, you might consider explicitly returning "self"
in cases where you intend the return value to be used.11
if we wish to do math on it in the future.
11 And why didn't the designers default the
return value to nil? Perhaps they didn't appreci-
ate the value of void functions. After all, at
the time Smalltalk was being designed, C didn't
even have a void data type.
-17-
4.5. Looking at our Account
Let's create an instance of class Account:
Smalltalk at: #a put: (Account new) !
Can you guess what this does? The "Smalltalk at: #a put:
<something>" hearkens back to chapter 2-it creates a
Smalltalk variable. And the "Account new" creates a new
Account, and returns it. So this line creates a Smalltalk
variable named "a", and attaches it to a new Account-all in
one line.
Let's take a look at the Account object we just cre-
ated:
a printNl !
It prints:
an Account
Hmmm... not very informative. The problem is that we didn't
tell our Account how to print itself, so we're just getting
the default system "printNl" method-which tells what the
object is, but not what it contains. So clearly we must add
such a method:
!Account methodsFor: 'printing'!
printOn: stream
super printOn: stream.
' with balance: ' printOn: stream.
balance printOn: stream
!!
Now give it a try again:
a printNl !
which prints:
an Account with balance: 0
This may seem a little strange. We added a new method,
printOn:, and our printNl message starts behaving differ-
ently. It turns out that the printOn: message is the cen-
tral printing function-once you've defined it, all of the
other printing methods end up calling it. Its argument is a
place to print to-quite often it is the variable "stdout".
This variable is usually hooked to your terminal, and thus
you get the printout to your screen.
The "super printOn: stream" lets our parent do what it
did before-print out what our type is. The "an Account"
part of the printout came from this. "' with balance: '
printOn: stream" creates the string " with balance: ", and
prints it out to the stream, too. Finally, "balance
printOn: stream" asks whatever object is hooked to the "bal-
ance" variable to print itself to the stream. We set "bal-
ance" to 0, so the 0 gets printed out.
4.6. Moving money around
We can now create accounts, and look at them. As it
stands, though, our balance will always be 0-what a tragedy!
Our final methods will let us deposit and spend money.
They're very simple:
-18-
!Account methodsFor: 'moving money'!
spend: amount
balance := balance - amount
!
deposit: amount
balance := balance + amount
!!
With these methods you can now deposit and spend amounts of
money. Try these operations:
a deposit: 125!
a deposit: 20!
a printNl!
a spend: 10!
a printNl!
4.7. Specialized objects
We now have a generic concept, "Account". We can cre-
ate them, check their balance, and move money in and out of
them. They provide a good foundation, but leave out impor-
tant information that particular types of accounts might
want. In the next chapter, we'll take a look at fixing this
problem using subclasses.
-19-
5. Two Subclasses for the Account Class
This chapter continues from the previous chapter in
demonstrating how one creates classes and subclasses in
Smalltalk. In this chapter we will create two special sub-
classes of Account, known as Checking and Savings. We will
continue to inherit the capabilities of Account, but will
tailor the two kinds of objects to better manage particular
kinds of accounts.
5.1. The Savings class
We create the Savings class as a subclass of Account.
It holds money, just like an Account, but has an additional
property that we will model: it is paid interest based on
its balance. We create the class Savings as a subclass of
Account:
Account subclass: #Savings
instanceVariableNames: 'interest'
classVariableNames: ''
poolDictionaries: ''
category: nil !
The instance variable "interest" will accumulate interest
paid. Thus, in addition to the "spend:" and "deposit:" mes-
sages which we inherit from our parent, Account, we will
need to define a method to add in interest deposits, and a
way to clear the interest variable (which we would do
yearly, after we have paid taxes). We first define a method
for allocating a new account-we need to make sure that the
interest field starts at 0.
!Savings methodsFor: 'initialization'!
init
interest := 0.
^ super init
!!
Recall that the parent took care of the "new" message, and
created a new object of the appropriate size. After cre-
ation, the parent also sent an "init" message to the new
object. As a subclass of Account, the new object will
receive the "init" message first; it sets up its own
instance variable, and then passes the "init" message up the
chain to let its parent take care of its part of the ini-
tialization.
With our new "Savings" account created, we can define
two methods for dealing specially with such an account:
-20-
!Savings methodsFor: 'interest'!
interest: amount
interest := interest + amount.
self deposit: amount
!
clearInterest
| oldinterest |
oldinterest := interest.
interest := 0.
^oldinterest
!!
The first method says that we add the "amount" to our run-
ning total of interest. The line "self deposit: amount"
tells Smalltalk to send ourselves a message, in this case
"deposit: amount". This then causes Smalltalk to look up
the method for "deposit:", which it finds in our parent,
Account. Executing this method then updates our overall
balance.12
One may wonder why we don't just replace this with the
simpler "balance := balance + amount". The answer lies in
one of the philosophies of object-oriented languages in gen-
eral, and Smalltalk in particular. Our goal is to encode a
technique for doing something once only, and then re-using
that technique when needed. If we had directly encoded
"balance := balance + amount" here, there would have been
two places that knew how to update the balance from a
deposit. This may seem like a useless difference. But con-
sider if later we decided to start counting the number of
deposits made. If we had encoded "balance := balance +
amount" in each place that needed to update the balance, we
would have to hunt each of them down in order to update the
12 "self" is much like "super", except that
"self" will start looking for a method at the bot-
tom of the type hierarchy for the object, but
"super" starts looking one level up from the cur-
rent level. Thus, using "super" forces inheri-
tance, but "self" will find the first definition
of the message which it can.
-21-
count of deposits. By sending "self" the message
"deposit:", we need only update this method once; each
sender of this message would then automatically get the cor-
rect up-to-date technique for updating the balance.
The second method, "clearInterest", is simpler. We
create a temporary variable "oldinterest" to hold the cur-
rent amount of interest. We then zero out our interest to
start the year afresh. Finally, we return the old interest
as our result, so that our year-end accountant can see how
much we made.13
5.2. The Checking class
Our second subclass of Account represents a checking
account. We will keep track of two facets:
- What check number we are on
- How many checks we have left in our checkbook
We will define this as another subclass of Account:
Account subclass: #Checking
instanceVariableNames: 'checknum checksleft'
classVariableNames: ''
poolDictionaries: ''
category: nil !
We have two instance variables, but we really only need to
initialize one of them-if there are no checks left, the cur-
rent check number can't matter. Remember, our parent class
Account will send us the "init" message. We don't need our
own class-specific "new" function, since our parent's will
provide everything we need.
!Checking methodsFor: 'Initialization'!
init
checksleft := 0.
^super init
!!
As in Savings, we inherit most of abilities from our super-
class, Account. For initialization, we leave "checknum"
13 Of course, in a real accounting system we
would never discard such information-we'd probably
throw it into a Dictionary object, indexed by the
year that we're finishing. The ambitious might
want to try their hand at implementing such an
enhancement.
-22-
alone, but set the number of checks in our checkbook to
zero. We finish by letting our parent class do its own ini-
tialization.
5.3. Writing checks
We will finish this chapter by adding a method for
spending money through our checkbook. The mechanics of tak-
ing a message and updating variables should be familiar:
!Checking methodsFor: 'spending'!
newChecks: number count: checkcount
checknum := number.
checksleft := checkcount
!
writeCheck: amount
| num |
num := checknum.
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount.
^ num
!!
"newChecks:" fills our checkbook with checks. We record
what check number we're starting with, and update the count
of the number of checks in the checkbook.
"writeCheck:" merely notes the next check number, then
bumps up the check number, and down the check count. The
message "self spend: amount" resends the message "spend:" to
our own object. This causes its method to be looked up by
Smalltalk. The method is then found in our parent class,
Account, and our balance is then updated to reflect our
spending.
You can try the following examples:
Smalltalk at: #c put: (Checking new) !
c printNl !
c deposit: 250 !
c printNl !
c newChecks: 100 count: 50 !
c printNl !
(c writeCheck: 32) printNl !
c printNl !
For amusement, you might want to add a printOn: message to
the checking class so you can see the checking-specific
information.
In this chapter, you have seen how to create subclasses
of your own classes. You have added new methods, and inher-
ited methods from the parent classes. These techniques pro-
vide the majority of the structure for building solutions to
problems. In the following chapters we will be filling in
details on further language mechanisms and types, and pro-
viding details on how to debug software written in
Smalltalk.
-23-
6. Code blocks
The Account/Saving/Checking example from the last chap-
ter has several deficiencies. It has no record of the
checks and their values. Worse, it allows you to write a
check when there are no more checks-the Integer value for
the number of checks will just calmly go negative! To fix
these problems we will need to introduce more sophisticated
control structures.
6.1. Conditions and decision making
Let's first add some code to keep you from writing too
many checks. We will simply update our current method for
the Checking class; if you have entered the methods from the
previous chapters, the old definition will be overridden by
this new one.
!Checking methodsFor: 'spending'!
writeCheck: amount
| num |
(checksleft < 1)
ifTrue: [ ^self error: 'Out of checks' ].
num := checknum.
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount
^ num
!!
The two new lines are:
(checksleft < 1)
ifTrue: [ ^self error: 'Out of checks' ].
At first glance, this appears to be a completely new struc-
ture. Look again! The only new construct is the square
brackets.
The first line is a simple boolean expression. "check-
sleft" is our integer, as initialized by our Checking class.
It is sent the message "<", and the argument 1. The current
number bound to "checksleft" compares itself against 1, and
returns a boolean object telling whether it is less than 1.
Now this boolean-being either true or false-is sent the
message "IfTrue:", with an argument which is called a code
block. A code block is an object, just like any other. But
instead of holding a number, or a Set, it holds executable
statements.
So what does a boolean do with a code block which is an
argument to a ifTrue: message? It depends on which boolean!
If the object is the "true" object, it executes the code
block it has been handed. If it is the "false" object, it
returns without executing the code block. So the tradi-
tional "conditional construct" has been replaced in
Smalltalk with boolean objects which execute the indicated
code block or not, depending on their truth-value.14
-24-
In the case of our example, the actual code within the
block sends an error message to the current object. error:
is handled by the parent class Object, and will pop up an
appropriate complaint when the user tries to write too many
checks. In general, the way you handle a fatal error in
Smalltalk is to send an error message to yourself (through
the "self" pseudo-variable), and let the error handling
mechanisms inherited from the Object class take over.
As you might guess, there is also an ifFalse: message
which booleans accept. It works exactly like ifTrue:,
except that the logic has been reversed; a boolean "false"
will execute the codeblock, and a boolean "true" will not.
You should take a little time to play with this method
of representing conditionals. You can run your checkbook,
but can also invoke the conditional functions directly:
true ifTrue: [ 'Hello, world!' printNl ] !
false ifTrue: [ 'Hello, world!' printNl ] !
true ifFalse: [ 'Hello, world!' printNl ] !
false ifFalse: [ 'Hello, world!' printNl ] !
6.2. Iteration and collections
Now that we have some sanity checking in place, it
remains for us to keep a log of the checks we write. We
will do so by adding a Dictionary object to our Checking
class, logging checks into it, and providing some messages
for querying our check-writing history. But this enhance-
ment brings up a very interesting question-when we change
the "shape" of an object (in this case, by adding a new
instance variable to the Checking class-our dictionary),
what happens to the existing class, and its objects?
The answer is that the old objects continue to exist
with their current shape.15 New objects will have the new
14 It is interesting to note that because of the
way conditionals are done, conditional constructs
are not part of the Smalltalk language-they are
merely a defined behavior for the Boolean class of
objects.
15 This is the case in GNU Smalltalk. Other
implementations will refuse to redefine the class
-25-
shape. As this can lead to very puzzling behavior, it is
usually best to eradicate all of the old objects, and then
implement your changes. If this were more than a toy object
accounting system, this would probably entail saving the
objects off, converting to the new class, and reading the
objects back into the new format. For now, we'll just
ignore what's currently there, and define our latest Check-
ing class.
Account subclass: #Checking
instanceVariableNames: 'checknum checksleft history'
classVariableNames: ''
poolDictionaries: ''
category: nil !
This is the same syntax as the last time we defined a
checking account, except that we have three instance vari-
ables-the "checknum" and "checksleft" which have always been
there, and our new "history" variable. We must now feed in
our definitions for each of the messages our object can han-
dle, since we are basically defining a new class under an
old name.16 Go ahead and do this now-the methods are in
chapter 5. We are using the same Account class, so you only
need to do the Checking methods.
With our new Checking instance variable, we are all set
to start recording our checking history. Our first change
will be in the "init" message handling:
!Checking methodsFor: 'initialization'!
init
checksleft := 0.
history := Dictionary new.
^ super init
!!
This provides us with a Dictionary, and hooks it to our new
until all of its instances have been hunted down
and eradicated!
16 Technically, GNU Smalltalk has associated us
with the existing class methods, but with a new
set of instance methods. It is often simpler to
work as if you had to define everything, rather
than trying to take advantage of this.
-26-
"history" variable.
Our next method records each check as it's written.
The method is a little more involved, as we've added some
more sanity checks to the writing of checks.
!Checking methodsFor: 'spending'!
writeCheck: amount
| num |
"Sanity check that we have checks left in our checkbook"
(checksleft < 1)
ifTrue: [ ^self error: 'Out of checks' ].
"Make sure we've never used this check number before"
num := checknum.
(history includesKey: num)
ifTrue: [ ^self error: 'Duplicate check number' ].
"Record the check number and amount"
history at: num put: amount.
"Update our next checknumber, checks left, and balance"
checknum := checknum + 1.
checksleft := checksleft - 1.
self spend: amount.
^ num
!!
We have added three things to our latest version of
writeCheck:. First, since our routine has become somewhat
involved, we have added comments. In Smalltalk, single
quotes are used for strings; double quotes enclose comments.
We have added comments before each section of code.
Second, we have added a sanity check on the check num-
ber we propose to use. Dictionary objects respond to the
includesKey: message with a boolean, depending on whether
something is currently stored under the given key in the
dictionary. If the check number is already used, the error:
message is sent to our object, aborting the operation.
Finally, we add a new entry to the dictionary. We have
already seen the at:put: message from chapter 2. Our use
here simply associates a check number with an amount of
money spent.17 With this, we now have a working Checking
17 You might start to wonder what one would do
if you wished to associate two pieces of informa-
tion under one key. Say, the value and who the
check was written to. There are several ways; the
-27-
class, with reasonable sanity checks and per-check informa-
tion.
Let us finish the chapter by enhancing our ability to
get access to all this information. We will start with some
simple print-out functions.
!Checking methodsFor: 'printing'!
printOn: stream
super printOn: stream.
', checks left: ' printOn: stream.
checksleft printOn: stream.
', checks written: ' printOn: stream.
(history size) printOn: stream.
!
check: num
| c |
c := history at: num ifAbsent: [ ^self error: 'No such check #' ].
^c
!!
There should be very few surprises here. We format and
print our information, while letting our parent classes han-
dle their own share of the work. When looking up a check
number, we once again take advantage of the fact that blocks
of executable statements are an object; in this case, we are
using the at:ifAbsent: message supported by the Dictionary
class. If the requested key value is not found in the dic-
tionary, the code block is executed. This allows us to cus-
tomize our error handling, as the generic error would only
tell the user "key not found".
While we can look up a check if we know its number, we
have not yet written a way to "riffle through" our collec-
tion of checks. The following function loops over the
checks, printing them out one per line. Because there is
best would probably be to create a new, custom
object which contained this information, and then
store this object under the check number key in
the dictionary. It would also be valid (though
probably over-kill) to store a dictionary as the
value-and then store as many pieces of information
as you'd like under each slot!
-28-
currently only a single numeric value under each key, this
might seem wasteful. But we have already considered storing
multiple values under each check number, so it is best to
leave some room for each item. And, of course, because we
are simply sending a printing message to an object, we will
not have to come back and re-write this code so long as the
object in the dictionary honors our printNl/printOn: mes-
sages.
!Checking methodsFor: 'printing'!
printChecks
history associationsDo: [:assoc|
(assoc key) print.
' - ' print.
(assoc value) printNl.
]
!!
We still see a code block object being passed to the dic-
tionary, but ":assoc|" is something new. A code block can
optionally receive arguments. In this case, the argument is
the key/value pair, known in Smalltalk as an Association.
This is the way that a dictionary object stores its
key/value pairs internally. In fact, when you sent an
at:put: message to a dictionary object, the first thing it
does is pack them into a new object from the Association
class. If you only wanted the value portion, you could call
history with a do: message instead.
Our code merely uses the "key" and "value" messages to
ask the association for the two values. We then invoke our
printing interface upon them. While the printNl message
implicitly uses "stdout", we don't want a newline until the
end, so the "print" message is used instead. It is pretty
much the same as "printNl", except it doesn't add a newline.
It is important that you be clear on the relationship
between an Association and the argument to a code block. In
this example, we passed a associationsDo: message to a dic-
tionary. A dictionary invokes the passed code block with an
Association when processing an associationsDo: message. But
code blocks can receive any type of argument-the type is
determined by the code which invokes the code block; Dic-
tionary, in this case. In the next chapter we'll see more
on how code blocks are used; we'll also look at how you can
invoke code blocks in your own code.
-29-
7. Code blocks, part two
In the last chapter, we looked at how code blocks could
be used to build conditional expressions, and how you could
iterate across all entries in a collection.18 We built our
own code blocks, and handed them off for use by system
objects. But there is nothing magic about invoking code
blocks; your own code will often need to do so. This chap-
ter will shows some examples of loop construction in
Smalltalk, and then demonstrate how you invoke code blocks
for yourself.
7.1. Integer loops
Integer loops are constructed by telling a number to
drive the loop. Try this example to count from 1 to 20:
1 to: 20 do: [:x| x printNl ] !
There's also a way to count up by more than one:
1 to: 20 by: 2 do: [:x| x printNl ] !
Finally, counting down is done with a negative interval:
20 to: 1 by: -1 do: [:x| x printNl ] !
7.2. Intervals
It is also possible to represent a range of numbers as
a standalone object. This allows you to represent a range
of numbers as a single object, which can be passed around
the system.
Smalltalk at: #i put: (Interval from: 5 to: 10) !
i printNl !
i do: [:x| x printNl] !
As with the integer loops, the Interval class can also
18 The do: message is understood by most types
of Smalltalk collections. It works for the Dic-
tionary class, as well as sets, arrays, strings,
intervals, linked lists, bags, and streams. The
associationsDo: message works only with dictionar-
ies. The difference is that do: passes only the
value portion, while associationsDo: passes the
entire key/value pair in an Association object.
-30-
represent steps greater than 1. It is done much like it was
for our numeric loop above:
i := (Interval from: 5 to: 10 by: 2)
i printNl !
i do: [:x| x printNl] !
7.3. Invoking code blocks
Let us revisit the checking example and add a method
for scanning only checks over a certain amount. This would
allow our user to find "big" checks, by passing in a value
below which we will not invoke their function. We will
invoke their code block with the check number as an argu-
ment; they can use our existing check: message to get the
amount.
!Checking methodsFor: 'scanning'!
checksOver: amount do: aBlock
history associationsDo: [:assoc|
((assoc value) > amount)
ifTrue: [aBlock value: (assoc key)]
]
!!
The structure of this loop is much like our printChecks mes-
sage from chapter 6. However, in this case we consider each
entry, and only invoke the supplied block if the check's
value is greater than the specified amount. The line:
ifTrue: [aBlock value: (assoc key)]
invokes the user-supplied block, passing as an argument the
association's key, which is the check number. The value:
message, when received by a code block, causes the code
block to execute. Code blocks take "value", "value:",
"value:value:", and "value:value:value:" messages, so you
can pass from 0 to 3 arguments to a code block.19 You might
find it puzzling that an association takes a "value" mes-
sage, and so does a code block. Remember, each object can
do its own thing with a message. A code block gets run when
it receives a "value" message. An association merely
returns the value part of its key/value pair. The fact that
both take the same message is, in this case, coincidence.
Let's quickly set up a new checking account with $250
(wouldn't this be nice in real life?) and write a couple
checks. Then we'll see if our new method does the job cor-
rectly:
19 There is also a valueWithArguments: message
which accepts an array holding as many arguments
as you would like.
-31-
Smalltalk at: #mycheck put: (Checking new) !
mycheck deposit: 250 !
mycheck newChecks: 100 count: 40 !
mycheck writeCheck: 10 !
mycheck writeCheck: 52 !
mycheck writeCheck: 15 !
mycheck checksOver: 1 do: [:x| printNl] !
mycheck checksOver: 17 do: [:x| printNl] !
mycheck checksOver: 200 do: [:x| printNl] !
We will finish this chapter with an alternative way of
writing our checksOver: code. In this example, we will use
the message select: to pick the checks which exceed our
value, instead of doing the comparison ourselves. We can
then invoke the new resulting collection against the user's
code block. Unlike our previous definition of
checksOver:do:, this one passes the user's code block the
association, not just a check number. How could this code
be rewritten to remedy this, while still using select:?
!Checking methodsFor: 'scanning'!
checksOver: amount do: aBlock
| chosen |
chosen := history select: [:amt| amt > amount].
chosen associationsDo: aBlock
!!
You can use the same set of tests that we ran above.
Notice that our code block:
[:x| x printNl]
now prints out an Association. This has the very nice
effect--with our old method, we were told which check num-
bers were above a given amount. With this new method, we
get the check number and amount in the form of an Associa-
tion. When we print an association, since the key is the
check number and the value is the check amount, we get a
list of checks over the amount in the format:
CheckNum -> CheckVal
-32-
8. When Things Go Bad
So far we've been working with examples which work the
first time. If you didn't type them in correctly, you prob-
ably received a flood of unintelligible complaints. You
probably ignored the complaints, and typed the example
again.
When developing your own Smalltalk code, however, these
messages are the way you find out what went wrong. Because
your objects, their methods, the error printout, and your
interactive environment are all contained within the same
Smalltalk session, you can use these error messages to debug
your code using very powerful techniques.
8.1. A Simple Error
First, let's take a look at a typical error. Type:
7 plus: 1 !
This will print out:
7 did not understand selector 'plus:'
UndefinedObject>>#executeStatements
UndefinedObject>>nil
The first line is pretty simple; we sent a message to the
"7" object which was not understood; not surprising since
the "plus" operation should have been "+". The two remain-
ing lines reflect the way the GNU Smalltalk invokes code
which we type to our command prompt; it generates a block of
code which is invoked via an internal function "executeS-
tatements". Thus, this output tells you that you directly
typed a line which sent an invalid message to the "7"
object.
The last two lines of the error output are actually a
stack backtrace. The most recent call is the one nearer the
top of the screen. In the next example, we will cause an
error which happens deeper within an object.
8.2. Nested Calls
Type the following lines:
Smalltalk at: #x put: (Dictionary new) !
x at: 1 !
The error you receive will look like:
Dictionary new: 32 "<0x33788>" error: key not found
MethodContext>>#value
Dictionary>>#at:ifAbsent:
Dictionary>>#at
UndefinedObject>>#executeStatements
UndefinedObject>>nil
The error itself is pretty clear; we asked for some-
thing within the Dictionary which wasn't there. The object
which had the error is identified as "Dictionary new: 32".
A Dictionary's default size is 32; thus, this is the object
we created with "Dictionary new".
The stack backtrace shows us the inner structure of how
a Dictionary responds to the at: message. Our hand-entered
-33-
command causes the usual two entries for "UndefinedObject".
Then we see a Dictionary object responding to an "at:" mes-
sage (the "Dictionary>>#at" line). This code called the
object with an "at:ifAbsent:" message. All of a sudden, a
different object receives a "value" message, and then the
error happened.
This isn't quite true; the error happened in the Dic-
tionary object. The mystery is where this "MethodContext"
came from. Fortunately, it isn't much of a mystery. The
answer lies in what we covered in the last two chapters:
code blocks.
A very common way to handle errors in Smalltalk is to
hand down a block of code which will be called when an error
occurs. For the Dictionary code, the "at:" message passes
in a block of code to the at:ifAbsent: code to be called
when "at:ifAbsent:" can't find the given key. Thus, without
even looking at the code for Dictionary itself, we can guess
that the Dictionary "at:" message handling looks something
like:
at: key ifAbsent: errCodeBlock
...look for key...
(keyNotFound) ifTrue: [ ^(errCodeBlock value) ]
...
at: key
^self at: key ifAbsent: [^self error: 'key not found']
The key is that we see in the stack backtrace that
at:ifAbsent: is called from at:, and a MethodContext is
called to give the error. Once we realize that a MethodCon-
text is just a fancy name for a code block, we can guess
that at: handed in a code block to print an error, and
at:ifAbsent: used the standard "value" message to invoke
this code block when it realized that the requested key
couldn't be found in the Dictionary.
It would be nice if each entry on the stack backtrace
included source line numbers. Unfortunately, at this point
GNU Smalltalk doesn't provide this feature. Of course, you
have the source code available....
8.3. Some Shortcomings in GNU Smalltalk
Unfortunately, there are some errors which GNU
Smalltalk is not very helpful in detecting. This informa-
tion applies to the latest version currently available-GNU
Smalltalk 1.1.1. Try indexing something which isn't any
sort of Collection:
(7 at: 99) printNl !
One would expect to receive an error,20 but instead GNU
20 And in fact, you will receive an error in
most Smalltalk systems.
-34-
Smalltalk simply returns the receiving object-7. Similarly,
one would expect Array bounds to be checked:
Smalltalk at: #x put: (Array new: 10) !
(x at: 7 put: 123) printNl !
(x at: 11 put: 1234) printNl !
(x at: 7) printNl !
(x at: 11) printNl !
But this example returns no error in GNU Smalltalk.21 When
an assignment to an array slot is correct, the returned
value is the value assigned-123 in this case. When you
assign outside the array bounds, you will receive the array
object itself as the result! Thus, if you accidentally
index your array incorrectly, you will have to figure out
what happened from a place further in your code where an
error crops up because your code was trying to operate upon
an element of the array, but instead is working on the array
itself.
8.4. Looking at Objects
When you are chasing an error, it is often helpful to
examine the instance variables of your objects. While
strategic "printNl"s will no doubt help, you can look at an
object without having to write all the code yourself. The
"inspect" message works on any object, and dumps out the
values of each instance variable within the object. Thus:
Smalltalk at: #x put: (Interval from: 1 to: 5) !
x inspect !
displays:
An instance of Interval
start: 1
stop: 5
step: 1
There's one thing the object inspector doesn't display-
the contents of an indexed class. Since we haven't looked
at this kind of object yet, we'll leave this to its own
chapter.
We'll finish this chapter by emphasizing a technique
which has already been covered-the use of the "error:" mes-
sage in your own objects. As you saw in the case of Dic-
tionary, an object can send itself an error: message with a
descriptive string to abort execution and dump a stack back-
trace. You should plan on using this technique in your own
objects. It can be used both for explicit user-caused
errors, as well as in internal sanity checks.
21 Again, it will error in most other Smalltalk
implementations.
-35-
9. Coexisting in the Class Hierarchy
The early chapters of this paper discussed classes in
one of two ways. The "toy" classes we developed were rooted
at Object; the system-provided classes were treated as
immutable entities. While one shouldn't modify the behavior
of the standard classes lightly, "plugging in" your own
classes in the right place among their system-provided
brethren can provide you powerful new classes with very lit-
tle effort.
This chapter will create two complete classes which
enhance the existing Smalltalk hierarchy. The discussion
will start with the issue of where to connect our new
classes, and then continue onto implementation. Like most
programming efforts, the result will leave many possibili-
ties for improvements. The framework, however, should begin
to give you an intuition of how to develop your own
Smalltalk classes.
9.1. The Existing Class Hierarchy
To discuss where a new class might go, it is helpful to
have a map of the current classes. The following is the
class hierarchy of GNU Smalltalk 1.1.1. Indentation means
that the line inherits from the earlier line with one less
level of indentation.22
22 This listing is courtesy of the printHierar-
chy method supplied by GNU Smalltalk author Steve
Byrne. If you have the GNU Smalltalk source, it's
in the samples/ directory.
-36-
Object
Autoload
Behavior
ClassDescription
Class
Metaclass
BlockContext
Boolean
False
True
CFunctionDescriptor
CObject
Collection
Bag
MappedCollection
SequenceableCollection
ArrayedCollection
Array
ByteArray
CompiledMethod
String
Symbol
Interval
LinkedList
Semaphore
OrderedCollection
SortedCollection
Set
Dictionary
IdentityDictionary
SystemDictionary
Delay
FileSegment
Link
Process
SymLink
Magnitude
Character
Date
LookupKey
Association
Number
Float
Integer
Time
Memory
ByteMemory
WordMemory
Message
MethodContext
MethodInfo
ProcessorScheduler
SharedQueue
Stream
-37-
PositionableStream
ReadStream
WriteStream
ReadWriteStream
FileStream
Random
TokenStream
UndefinedObject
While initially a daunting list, you should take the
time to hunt down the classes we've examined in this paper
so far. Notice, for instance, how an Array is a subclass
below the "SequenceableCollection" class. This makes sense;
you can walk an Array from one end to the other. By con-
trast, notice how a Set is at the same level as Sequence-
ableCollection. It doesn't make sense to walk a Set from
one end to the other.
A little puzzling is the relationship of a Dictionary
to a Set; why is a Dictionary a subclass of a Set? The
answer lies in the basic structure of both a Set and a Dic-
tionary. Both hold an unordered collection of objects. For
a set, they're any objects; for a Dictionary, they are Asso-
ciations. Thus, Dictionary inherits some of the more basic
mechanisms for creating itself, and then adds an extra layer
of interpretation.
Finally, look at the treatment of numbers-starting with
the class Magnitude. While numbers can be ordered by "less
than", "greater than", and so forth, so can a number of
other objects. Each subclass of Magnitude is such an
object. So we can compare characters with other characters,
dates with other dates, and times with other times, as well
as numbers with numbers.23
9.2. Those Darn Arrays
Imagine that you're chasing an array problem, and the
lack of a clear bounds check is making it too hard. You
could modify the Smalltalk implementation, but perhaps it's
in somebody else's directory, so it wouldn't be practical.
Why not add a subclass, put some sanity checks in the array
indexing, and use our superclass to do all the work?
23 Ignore LookupKey; its presence appears to be
historical.
-38-
Array variableSubclass: #CheckedArray
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: nil !
!CheckedArray methodsFor: 'bounds checking'!
boundsCheck: index
((index < 1) | (index > (self basicSize))) ifTrue: [
^self illegalIndex
]
!
illegalIndex
^self error: 'Illegal index'
!!
!CheckedArray methodsFor: 'basic'!
at: index
self boundsCheck: index.
^super at: index
!
at: index put: val
self boundsCheck: index.
^super at: index put: val
!!
Much of the machinery of adding a class should be
familiar. Instead of our usual subclass: message, we use a
variableSubclass: message. This reflects the underlying
structure of an Array object; we'll delay discussing this
until the chapter on the nuts and bolts of arrays. In any
case, we inherit all of the actual knowledge of how to cre-
ate arrays, reference them, and so forth. All that we do is
intercept at: and at:put: messages, and call our common
function to validate the array index. The way that we coded
the bounds check bears a little examination.
Making a first cut at coding the bounds check, you
might have coded the bounds check in CheckedArray's methods
twice-once for at:, and again for at:put:. As always, it's
preferable to code things once, and then re-use them. So we
instead add a method for bounds checking "boundsCheck:", and
use it for both cases. If we ever wanted to enhance the
bounds checking (perhaps enhance the error message to print
the offending index value?), we only have to change it in
one place.
The actual math for calculating whether the bounds have
been violated is a little interesting. The first part of
the expression:
((index < 1) | (index > (self basicSize)))
is true if the index is less than 1, otherwise it's false.
This part of the expression thus becomes the boolean object
true or false. The boolean object then receives the message
"|", and the argument "(index > (self basicSize))". "|"
means "or"-we want to OR together the two possible illegal
-39-
range checks. What is the second part of the expression?24
"index" is our argument, an integer; it receives the
message ">", and thus will compare itself to the value "self
basicSize" returns. While we haven't covered the underlying
structures Smalltalk uses to build arrays, we can briefly
say that the "basicSize" message returns the number of ele-
ments the Array object can contain. So the index is checked
to see if it's less than 1 (the lowest legal Array index) or
greater than the highest allocated slot in the Array. If it
is either (the "|" operator), the expression is true, other-
wise false.
From there it's downhill; our boolean object receives
the ifTrue: message, and a code block which will send an
error message to the object. Why do we have a separate mes-
sage just to print the error? For purposes of this example,
it's not needed. But one could conceive, in general, of a
couple of different sanity checks all sharing the same mech-
anism for actually printing the error message. So we'll
write it this way anyway.
24 Smalltalk also offers an "or:" message, which
is different in a subtle way from "|". or: takes
a code block, and only invokes the code block if
it's necessary to determine the value of the
expression. This is analogous to the guaranteed C
semantic that "&&" evaluates left-to-right only as
far as needed. We could have written the expres-
sions as "((index < 1) or: [index > (self basic-
Size)])". Since we expect both sides of or: to be
false most of the time, there isn't much reason to
delay evaluation of either side.
-40-
9.3. Adding a New Kind of Number
If we were programming an application which did a large
amount of complex math, we could probably manage it with a
number of two-element arrays. But we'd forever be writing
in-line code for the math and comparisons; it would be much
easier to just implement an object class to support the com-
plex numeric type. Where in the class hierarchy would it be
placed?
You've probably already guessed-but let's step down the
hierarchy anyway. Everything inherits from Object, so
that's a safe starting point. Complex numbers can not be
compared with "<" and ">", and yet we strongly suspect that,
since they are numbers, we should place them under the Num-
ber class. But Number inherits from Magnitude--how do we
resolve this conflict? A subclass can place itself under a
superclass which allows some operations the subclass doesn't
wish to allow. All that you must do is make sure you inter-
cept these messages and return an error. So we will place
our new Complex class under Number, and make sure to disal-
low comparisons.
One can reasonably ask whether the real and imaginary
parts of our complex number will be integer or floating
point. In the grand Smalltalk tradition, we'll just leave
them as objects, and hope that they respond to numeric mes-
sages reasonably. If they don't, the user will doubtless
receive errors and be able to track back their mistake with
little fuss.
We'll define the four basic math operators, as well as
the (illegal) relationals. We'll add printOn: so that the
printing methods work, and that should give us our Complex
class. The class as presented suffers some limitations,
which we'll cover later in the chapter.
-41-
Number subclass: #Complex
instanceVariableNames: 'realpart imagpart'
classVariableNames: ''
poolDictionaries: ''
category: nil !
!Complex class methodsFor: 'creating'!
new
^self error: 'use real:imaginary:'
!
new: ignore
^self new
!
real: r imaginary: i
^(super new) setReal: r setImag: i
!!
!Complex methodsFor: 'creating--private'!
setReal: r setImag: i
realpart := r.
imagpart := i.
^self
!!
!Complex methodsFor: 'basic'!
real
^realpart
!
imaginary
^imagpart
!!
!Complex methodsFor: 'math'!
+ val
^Complex real: (realpart + (val real))
imaginary: (imagpart + (val imaginary))
!
- val
^Complex real: (realpart - (val real))
imaginary: (imagpart - (val imaginary))
!
* val
^Complex real: ((realpart * (val real)) - (imagpart * (val imaginary)))
imaginary: ((realpart * (val imaginary)) +
(imagpart * (val real)))
!
/ val
| d r i |
d := ((val real) * (val real)) + ((val imaginary) * (val imaginary)).
r := ((realpart * (val real)) + (imagpart * (val imaginary))) / d.
i := ((imagpart * (val real)) - (realpart * (val imaginary))) / d.
^Complex real: r imaginary: i
!!
!Complex methodsFor: 'comparison'!
-42-
= val
^((realpart = (val real)) & (imagpart = (val imaginary)))
!
> val
^self shouldNotImplement
!
>= val
^self shouldNotImplement
!
< val
^self shouldNotImplement
!
<= val
^self shouldNotImplement
!!
!Complex methodsFor: 'printing'!
printOn: aStream
aStream nextPut: $(.
realpart printOn: aStream.
aStream nextPut: $,.
imagpart printOn: aStream.
aStream nextPut: $)
!!
There should be surprisingly little which is actually
new in this example. The printing method uses both printOn:
as well as nextPut: to do its printing. While we haven't
covered it, it's pretty clear that "$(" generates the ASCII
character "(" as an object, and nextPut: puts its argument
as the next thing on the stream.
The math operations all generate a new object, calcu-
lating the real and imaginary parts, and invoking the Com-
plex class to create the new object. Our creation code is a
little more compact than earlier examples; instead of using
a local variable to name the newly-created object, we just
use the return value and send a message directly to the new
object. Our initialization code explicitly returns self;
what would happen if we left this off?25
9.4. Inheritance and Polymorphism
This is a good time to look at what we've done with the
two previous examples at a higher level. With the
CheckedArray class, we inherited almost all of the function-
ality of arrays, with only a little bit of code added to
25 Hint: consider what the default return value
is when no explicit value is provided. This was
covered in chapter 4.
-43-
address our specific needs. While you may have not thought
to try it, all the existing methods for an Array continue to
work without further effort-you might find it interesting to
ponder why the following still works:
Smalltalk at: #a put: (CheckedArray new: 10) !
a at: 5 put: 1234 !
a do: [:i| i printNl ] !
The strength of inheritance is that you focus on the incre-
mental changes you make; the things you don't change will
generally continue to work.
In the Complex class, the value of polymorphism was
exercised. A Complex number responds to exactly the same
set of messages as any other number. If you had handed this
code to someone, they would know how to do math with Complex
numbers without further instruction. Compare this with C,
where a complex number package would require the user to
first find out if the complex-add function was com-
plex_plus(), or perhaps complex_add(), or add_complex(),
or....
9.5. Limitations of the Complex Class
One glaring deficiency is present in the Complex class-
what happens if you mix normal numbers with Complex numbers?
Currently, the Complex class assumes that it will only
interact with other Complex numbers. But this is unrealis-
tic-mathematically, a "normal" number is simply one with an
imaginary part of 0. Smalltalk was designed to allow num-
bers to coerce themselves into a form which will work with
other numbers.
The system is clever and requires very little addi-
tional code. Unfortunately, it would have tripled the
amount of explanation required. If you're interested in how
coercion works in GNU Smalltalk, you should find the
Smalltalk library source, and trace back the execution of
the retry:coercing: messages. You want to consider the
value which the "generality" message returns for each type
of number. Finally, you need to examine the coerce: han-
dling in each numeric class.
-44-
10. Smalltalk Streams
Our examples have used a mechanism extensively, even
though we haven't discussed it yet. The Stream class pro-
vides a framework for a number of data structures, including
input and output functionality, queues, and endless sources
of dynamically-generated data. A Smalltalk stream is quite
similar to the UNIX streams you've used from C. A stream
provides a sequential view to an underlying resource; as you
read or write elements, the stream position advances until
you finally reach the end of the underlying medium. Most
streams also allow you to set the current position, provid-
ing random access to the medium.
10.1. The Output Stream
The examples in this book all work because they write
their output to the stdout stream. Each class implements
the printOn: method, and writes its output to the supplied
stream. The printNl method all objects use is simply to
send the current object a printOn: message whose argument is
stdout. You can invoke the standard output stream directly:
'Hello, world' printOn: stdout !
stdout inspect !
10.2. Your Own Stream
Unlike a pipe you might create in C, the underlying
storage of a Stream is under your control. Thus, a Stream
can provide an anonymous buffer of data, but it can also
provide a stream-like interpretation to an existing array of
data. Consider this example:
Smalltalk at: #a put: (Array new: 10) !
a at: 4 put: 1234 !
a at: 9 put: 5678 !
Smalltalk at: #s put: (ReadWriteStream on: a) !
s inspect !
s position: 1 !
s inspect !
s nextPut: 11; nextPut: 22 !
(a at: 1) printNl !
a do: [:x| x printNl] !
s position: 2 !
s do: [:x| x printNl] !
s position: 5 !
s do: [:x| x printNl] !
s inspect !
The key is the on: message; it tells a stream class to
create itself in terms of the existing storage. Because of
polymorphism, the object specified by on: does not have to
be an Array; any object which responds to numeric at: mes-
sages can be used. If you happen to have the CheckedArray
class still loaded from the previous chapter, you might try
streaming over that kind of array instead.
You're wondering if you're stuck with having to know
how much data will be queued in a Stream at the time you
create the stream. If you use the right class of stream,
-45-
the answer is no. A ReadStream provides read-only access to
an existing collection. You will receive an error if you
try to write to it. If you try to read off the end of the
stream, you will also get an error.
By contrast, WriteStream and ReadWriteStream (used in
our example) will tell the underlying collection to grow
(using the "grow" message) when you write off the end of the
existing collection. Thus, if you want to write several
strings, and don't want to add up their lengths yourself:
Smalltalk at: #s put: (ReadWriteStream on: (String new: 0)) !
s inspect !
'Hello, ' printOn: s !
s inspect !
'world' printOn: s !
s inspect !
s position: 1 !
s inspect !
s do: [:c| c printOn: stdout] !
(s contents) printNl !
In this case, we have used a String as the collection
for the Stream. The printOn: messages add bytes to the ini-
tially empty string. Once we've added the data, you can
continue to treat the data as a stream. Alternatively, you
can ask the stream to return to you the underlying object.
After that, you can use the object (a String, in this exam-
ple) using its own access methods.
There are many amenities available on a stream object.
You can ask if there's more to read with "atEnd". You can
query the position with "position", and set it with "posi-
tion:". You can see what will be read next with "peek", and
you can read the next element with "next".
In the writing direction, you can write an element with
"nextPut:". You don't need to worry about objects doing a
printOn: with your stream as a destination; this operation
ends up as a sequence of nextPut:'s to your stream. If you
have a collection of things to write, you can use
"nextPutAll:" with the collection as an argument; each mem-
ber of the collection will be written onto the stream. If
you want to write an object to the stream several times, you
can use "next:put:":
Smalltalk at: #s put: (ReadWriteStream on: (Array new: 0)) !
s next: 4 put: 'Hi!' !
s position: 1 !
s do: [:x| x printNl] !
10.3. Files
Streams can also operate on files. If you wanted to
dump the file "/etc/passwd" to your terminal, you could cre-
ate a stream on the file, and then stream over its contents:
-46-
Smalltalk at: #f put: (FileStream open: '/etc/passwd' mode: 'r') !
f do: [:c| c printOn: stdout] !
f position: 30 !
1 to: 25 do: [(f next) printOn: stdout] !
f close !
and, of course, you can load Smalltalk source code:
FileStream fileIn: '/users/myself/src/source.st' !
10.4. Dynamic Strings
Streams provide a powerful abstraction for a number of
data structures. Concepts like current position, writing
the next position, and changing the way you view a data
structure when convenient combine to let you write compact,
powerful code. The last example is taken from the actual
Smalltalk source code-it shows a general method for making
an object print itself onto a a string.
printString
| stream |
stream := WriteStream on: (String new: 0).
self printOn: stream.
^stream contents
!
This method, residing in Object, is inherited by every
class in Smalltalk. The first line creates a WriteStream
which stores on a String whose length is currently 0. It
then invokes the current object with printOn:. As the
object prints itself to "stream", the String grows to accom-
modate new characters. When the object is done printing,
the method simply returns the underlying string.
As we've written code, the assumption has been that
printOn: would go to the terminal. But replacing a stream
to a file (/dev/tty) with a stream to a data structure
(String new: 0) works just as well. The last line tells the
Stream to return its underlying collection-which will be the
string which has had all the printing added to it. The
result is that the printString message returns an object of
the String class whose contents are the printed representa-
tion of the object receiving the printString message.
-47-
11. How Arrays Work
Smalltalk provides a very adequate selection of prede-
fined classes from which to choose. Eventually, however,
you will find the need to code a new basic data structure.
Because Smalltalk's most fundamental storage allocation
facilities are arrays, it is important that you understand
how to use them to gain efficient access to this kind of
storage.
11.1. The Array Class
Our examples have already shown the Array class, and
its use is fairly obvious. For many applications, it will
fill all your needs-when you need an array in a new class,
you keep an instance variable, allocate a new Array and
assign it to the variable, and then send array accesses via
the instance variable.
This technique even works for string-like objects,
although it is wasteful of storage. An Array object uses a
Smalltalk pointer for each slot in the array; its exact size
is transparent to the programmer, but you can generally
guess that it'll be roughly the word size of your machine.26
For storing an array of characters, therefore, an Array
works but is inefficient.
11.2. Arrays at a Lower Level
So let's step down to a lower level of data structure.
a ByteArray is much like an Array, but each slot holds only
an integer from 0 to 255-and each slot uses only a byte of
storage. If you only needed to store small quantities in
each array slot, this would therefore be a much more effi-
cient choice than an Array. As you might guess, this is the
type of array which a String uses.
Aha! But when you go back to chapter 9 and look at the
Smalltalk hierarchy, you notice that String does not inherit
from ByteArray. To see why, we must delve down yet another
level, and arrive at the basic methods for compiling a
class.
For most example classes, we've used the message:
subclass:
instanceVariableNames:
classVariableNames:
poolDictionaries:
category:
But when we implemented our CheckedArray example, we used
"variableSubclass:" instead of just "subclass:". The choice
of these two kinds of class creation (and a third we'll show
shortly) defines the fundamental structure of Smalltalk
objects created within a given class. Let's consider the
differences in the next three sub-sections.
26 32 bits for most ports of GNU Smalltalk.
-48-
11.2.1. subclass:
This kind of class creation specifies the simplest
Smalltalk object. The object consists only of the storage
needed to hold the instance variables. In C, this would be
a simple structure with zero or more scalar fields.27
11.2.2. variableSubclass:
This type of class is a superset of a subclass:. Stor-
age is still allocated for any instance variables, but the
objects of the class must be created with a new: message.
The number passed as an argument to new: causes the new
object, in addition to the space for instance variables, to
also have that many slots of storage allocated. The analog
in C would be to have a structure with some scalar fields,
followed at its end by an array of the requested size of
pointers.
11.2.3. variableByteSubclass:
This is a special case of variableSubclass:; the stor-
age allocated as specified by new: is an array of bytes.
The analog in C would be a structure with scalar fields,
followed by an array of char.
11.3. Accessing These New Arrays
You already know how to access instance variables-by
name. But there doesn't seem to be a name for this new
storage. The way an object accesses it is to send itself
array-type messages-at:, at:put:, and so forth.
The problem is when an object wants to add a new level
of interpretation to the at: and at:put: messages. Consider
a Dictionary-it is a variableSubclass: type of object, but
its at: message is in terms of a key, not an integer index
of its storage. Since it has redefined the at: message, how
does it access its fundamental storage?
The answer is that Smalltalk has defined basicAt: and
basicAt:put:, which will access the basic storage even when
the at: and at:put: messages have been defined to provide a
different abstraction.
11.4. An Example
This can get pretty confusing in the abstract, so let's
do an example to show how it's pretty simple in practice.
Smalltalk arrays tend to start at 1; let's define an array
type whose permissible range is arbitrary.
27 C requires one or more; zero is allowed in
Smalltalk.
-49-
ArrayedCollection variableSubclass: 'RangedArray'
instanceVariableNames: 'base'
classVariableNames: ''
poolDictionaries: ''
category: nil !
RangedArray comment: 'I am an Array whose base is arbitrary' !
!RangedArray class methodsFor: 'creation'!
new
^self error: 'Use new:base:'
!
new: ignore
^self new
!
new: size base: b
^(super new: size) init: b
!!
!RangedArray methodsFor: 'init'!
init: b
base := (b - 1). "- 1 because basicAt: works with a 1 base"
^self
!!
!RangedArray methodsFor: 'basic'!
rangeCheck: i
((i <= base) | (i > (base + (self basicSize)))) ifTrue: [
'Bad index value: ' printOn: stderr.
i printOn: stderr.
(Character nl) printOn: stderr.
^self error: 'illegal index'
]
!
at: i
self rangeCheck: i.
^self basicAt: (i-base)
!
at: i put: v
self rangeCheck: i.
^self basicAt: (i-base) put: v
!!
The code has two parts; an initialization, which simply
records what index you wish the array to start with, and the
at: messages, which adjust the requested index so that the
underlying storage receives its 1-based index instead.
We've included a range check much like CheckedArray; its
utility will demonstrate itself in a moment:
Smalltalk at: #a put: (RangedArray new: 10 base: 5) !
a at: 5 put: 0 !
a at: 4 put: 1 !
Since 4 is below our base of 5, a range check error occurs.
But this check can catch more than just our own misbehavior!
a do: [:x| x printNl] !
Our do: message handling is broken! The stack backtrace
pretty much tells the story:
-50-
RangedArray>>#rangeCheck:
RangedArray>>#at:
MethodContext>>#value:
Integer>>#to:by:do:
Integer>>#to:do:
RangedArray>>#do:
Our code received a do: message. We didn't define one, so
we inherited the existing do: handling. We see that an
Integer loop was constructed, that a code block was invoked,
and that our own at: code was invoked. When we range
checked, we trapped an illegal index. Just by coincidence,
this version of our range checking code also dumps the
index. We see that do: has assumed that all arrays start at
1.
The immediate fix is obvious; we implement our own do:
!RangedArray methodsFor: 'basic'!
do: aBlock
1 to: (self basicSize) do: [:x|
aBlock value: (self basicAt: x)
]
!!
But the issues start to run deep. If our parent class
believed that it knew enough to assume a starting index of
1, why didn't it also assume that it could call basicAt:?
Object-oriented methodology says that one object should be
entirely opaque to another. But what sort of privacy should
there be between a higher class and its subclasses? How
many assumption can a subclass make about its superclass,
and how many can the superclass make before it begins
infringing on the sovereignty of its subclasses? There are
rarely easy answers.
11.5. Basic Allocation
In this chapter, we've seen the fundamental mechanisms
used to allocate and index storage. When the storage need
not be accessed with peak efficiency, you can use the exist-
ing array classes. When every access counts, having the
storage be an integral part of your own object allows for
the quickest access. When you move into this area of object
development, inheritance and polymorphism become trickier;
each level must coordinate its use of the underlying array
with other levels.
-51-
12. Further Studies
The question is always how far to go in one document.
At this point, you know how to create classes. You know how
to use inheritance, polymorphism, and the basic storage man-
agement mechanisms of Smalltalk. You've also seen a sam-
pling of Smalltalk's powerful classes. The rest of this
chapter simply points out areas for further study; perhaps a
newer version of this document might cover these in further
chapters.
12.1. Viewing the Smalltalk Source Code
Depending on the thoroughness of the person who
installed GNU Smalltalk, it's possible to view the source
code for a system method. For instance, to see how a Dic-
tionary processes a do: message:
Dictionary edit: #do: !
The viewer is hard-coded as emacs; this may or may not work
at your installation.
12.2. Other Ways to Collect Objects
We've seen Array, ByteArray, Dictionary, Set, and the
various streams. You'll want to look at the Bag,
LinkedList, and SortedCollection classes. For special pur-
poses you'll want to examine ByteMemory and WordMemory.
12.3. Flow of Control
GNU Smalltalk has rudimentary support for threads of
execution. The state is embodied in a Process class object;
you'll also want to look at the ProcessorScheduler class.
12.4. Smalltalk Virtual Machine
GNU Smalltalk is implemented as a virtual instruction
set. By invoking GNU Smalltalk with the -d option, you can
view the byte opcodes which are generated as files on the
command line are loaded. Similarly, running GNU Smalltalk
with -e will trace the execution of instructions in your
methods.
You can look at the GNU Smalltalk source to gain more
information on the instruction set. A better first step if
you want to pursue this subject is to start with "A Little
Smalltalk"" by Tim Budd. The source code is freely avail-
able, and the book provides a solid introduction to
Smalltalk-type virtual machines. The canonical book is from
the original designers of Smalltalk:
Smalltalk-80: The Language and its Implementation
- Adele Goldberg and David Robson
12.5. Two Flavors of Equality
As first seen in chapter two, Smalltalk keys its dic-
tionary with things like "#word", whereas we generally use
"'word'". The former, as it turns out, is from class Sym-
bol. The latter is from class String. What's the real dif-
ferent between a Symbol and a String? To answer the ques-
tion, we'll use an analogy from C.
-52-
In C, if you have a function for comparing strings, you
might try to write it:
strcpy(char *p, char *q)
{
return (p == q);
}
But clearly this is wrong! The reason is that you can have
two copies of a string-each with the same contents-but each
at its own address. A correct string compare must walk its
way through the strings and compare each element.
In Smalltalk, exactly the same issue exists, although
the details of manipulating storage addresses are hidden.
If we have two Smalltalk strings, both with the same con-
tents, we don't necessarily know if they're at the same
storage address. In Smalltalk terms, we don't know if
they're the same object.
The Smalltalk dictionary is searched frequently. To
speed the search, it would be nice to not have to compare
the characters of each element, but only compare the address
itself. To do this, you need to have a guarantee that all
strings with the same contents are the same object. The
String class, created like:
y := 'Hello' !
does not satisfy this. Each time you execute this line, you
may well get a new object. But a very similar class, Sym-
bol, will always return the same object:
y := #Hello !
In general, you can use strings for almost all your tasks.
If you ever get into a performance-critical function which
looks up strings, you can switch to Symbol. It takes longer
to create a Symbol, and the memory for a Symbol is never
freed (since the class has to keep tabs on it indefinitely
to guarantee it continues to return the same object). You
can use it, but use it with care.
12.6. Checking for the Two Types of Equality
This paper has generally used the strcmp()-ish kind of
checks for equality. If you ever need to ask the question
"is this the same object?", you use the "==" operator
instead of "=":
Smalltalk at: #x put: 0 !
Smalltalk at: #y put: 0 !
x := 'Hello' !
y := 'Hello' !
(x = y) printNl !
(x == y) printNl !
x := #Hello !
y := #Hello !
(x = y) printNl !
(x == y) printNl !
Using C terms, the former compares contents like str-
cmp(). The latter compares storage addresses, like a
pointer comparison.
-53-
12.7. Where to get Help
The newsgroup comp.lang.smalltalk is read by many peo-
ple with a great deal of Smalltalk experience. There are
several commercial Smalltalk implementations; you can buy
support for these, though it isn't cheap. For the GNU
Smalltalk system in particular, you can try the newsgroup
gnu.smalltalk.bug. If all else fails, you can try the
author at:
jtk@netcom.com
No guarantees, but the author will do his best!
12.8. Acknowledgments
Thanks to Steve Byrne for writing GNU Smalltalk in the
first place. Great thanks to Mark Bush and Bob Roos for
their meticulous jobs of proofreading this document, and the
generous amounts of input they provided on refinements to
the contents and structure. Thanks also to Andrew Berg for
his comments on the early chapters of the document.
Any remaining errors are purely the fault of the
author. This document is provided as-is, without warranty,
but I will happily accept reports of any errors. If time
permits, I will perhaps even release a corrected revision of
the document.
I release this document into the public domain, and
simply request that you acknowledge me as the original
author in any use or derivative work you make of this docu-
ment.
Andy Valencia
325 Union Ave #359
Campbell, CA 95008
jtk@netcom.com
November 27, 1992
-54-
APPENDIX A
A Simple Overview of Smalltalk Syntax
Smalltalk's power comes from its treatment of objects.
In this document, we've mostly avoided the issue of syntax
by using strictly parenthesized expressions as needed. When
this leads to code which is hard to read due to the density
of parentheses, a knowledge of Smalltalk's syntax can let
you simplify expressions. In general, if it was hard for
you to tell how an expression would parse, it will be hard
for the next person, too.
The following presentation presents the grammar a cou-
ple of related elements at a time. We use a BNF style of
grammar, with some extensions. The form:
[ ... ]
means that "..." can occur zero or one times.
[ ... ]*
means zero or more;
[ ... ]+
means one or more.
... | ... [ | ... ]*
means that one of the variants must be chosen. Characters
in double quotes refer to the literal characters. Most ele-
ments may be separated by white space; where this is not
legal, the elements are presented without white space
between them.
methods: "!" id ["class"] "methodsFor:" string "!" [method "!"] "!"
Methods are introduced by first naming a class (the id ele-
ment), specifying "class" if you're adding class methods
instead of instance methods, and sending a string argument
to the methodsFor: message. Each method is terminated with
an "!"; two "!"'s in a row signify the end of the new meth-
ods.
method: message [prim] [temps] exprs
message: id | binsel id | [keysel id]+
prim: "<" "primitive:" number ">"
temps: "|" [id]* "|"
A method definition starts out with a kind of template. The
message to be handled is specified with the message names
spelled out and identifiers in the place of arguments. A
special kind of definition is the primitive; it has not been
covered in this paper; it provides an interface to the
underlying Smalltalk virtual machine. temps is the declara-
tion of local variables. Finally, exprs (covered soon) is
the actual code for implementing the method.
-55-
unit: id | literal | block | "(" expr ")"
unaryexpr: unit [ id ]+
primary: unit | unaryexpr
These are the "building blocks" of Smalltalk expressions. A
unit represents a single Smalltalk value, with the highest
syntactic precedence. A unaryexpr is simply a unit which
receives a number of unary messages. A unaryexpr has the
next highest precedence. A primary is simply a convenient
left-hand-side name for one of the above.
exprs: [expr "."]* [["^"] expr]
expr: [id ":="]* expr2
expr2: primary | msgexpr [ ";" cascade ]*
A sequence of expressions is separated by "."'s and can end
with a returned value ("^"). There can be leading assign-
ments; unlike C, assignments apply only to simple variable
names. An expression is either a primary (with highest
precedence) or a more complex message. cascade does not
apply to primary constructions, as they are too simple to
require the construct--since all primary construct are
unary, you can just add more unary messages:
1234 printNl printNl printNl !
msgexpr: unaryexpr | binexpr | keyexpr
A complex message is either a unary message (which we have
already covered), a binary message ("+", "-", and so forth),
or a keyword message ("at:", "new:", ....) Unary has the
highest precedence, followed by binary, and keyword messages
have the lowest precedence. Examine the two versions of the
following messages. The second have had parentheses added
to show the default precedence.
myvar at: 2 + 3 put: 4
mybool ifTrue: [ ^ 2 / 4 roundup ]
(myvar at: (2 + 3) put: (4))
(mybool ifTrue: ([ ^ (2 / (4 roundup)) ]))
cascade: id | binmsg | keymsg
A cascade is used to direct further messages to the same
object which was last used. The three types of messages (
id is how you send a unary message) can thus be sent.
binexpr: primary binmsg [ binmsg ]*
binmsg: binsel primary
binsel: selchar[selchar]
A binary message is sent to an object, which primary has
identified. Each binary message is a binary selector, con-
structed from one or two characters, and an argument which
is also provided by a examplesprimary.Some
1 + 2 - 3 / 4
which parses as:
(((1 + 2) - 3) / 4)
-56-
keyexpr: keyexpr2 keymsg
keyexpr2: binexpr | primary
keymsg: [keysel keyw2]+
keysel: id":"
Keyword expressions are much like binary expressions, except
that the selectors are made up of identifiers with a colon
appended. Where the arguments to a binary function can only
be from primary, the arguments to a keyword can be binary
expressions or primary ones. This is because keywords have
the lowest precedence.
block: "[" [[":" id]* "|" ] exprs "]"
A code block is square brackets around a collection of
Smalltalk expressions. The leading ": id" part is for block
arguments.
literal: number | string | charconst | symconst | arrayconst
arrayconst: "#" array
array: "(" [number | string | symbol | array | charconst]* ")"
number: [[dig]+ "r"] ["-"] [hexDig]+ ["." [hexDig]+] ["e"["-"][dig]+].
string: "'"[char]*"'"
charconst: "$"char
symconst: "$"symbol
We have already shown the use of many of these constants.
Although not covered in this paper, numbers can have a base
specified at their front, and a trailing scientific nota-
tion. We have seen examples of character, string, and sym-
bol constants. Array constants are simple enough; they
would look like:
Smalltalk at: #a put: #(1 2 'Hi' $x $Hello 4 5) !
symbol: id | binsel | keysel[keysel]*
Symbols are mostly used to represent the names of methods.
Thus, they can hold simple identifiers, binary selectors,
and keyword selectors:
$hello
$+
$at:put:
id: letter[letter|dig]*
selchar: "+" | "-" | "*" | "/" | "~" | "|" | "," |
"<" | ">" | "=" | "&"
hexdig: "0".."9" | "A".."F"
dig: "0".."9"
These are the categories of characters and how they are com-
bined at the most basic level. selchar simply lists the
characters which can be combined to name a binary message.